grammar Fibitos;

//Skaner

//class FibsLexer extends Lexer;

options {
//    charVocabulary = '\0'..'\377';
//    testLiterals=false;    // don't automatically test for literals
	k=6;                   
	output=AST;
	ASTLabelType=CommonTree; // type of $stat.tree ref etc...

}

@header {
	import java.util.Map;
	import java.util.TreeMap;
	import java.util.HashMap;
	import java.util.List;
	import java.util.LinkedList;
	
	import util.Attribute;
	import util.Result;
	import util.Type;
	import util.Reference;
	import util.Class;
	import util.Selector;
	import util.Scope;
	import util.MethodCall;
	import util.Method;
	
	import statement.*;
	import expression.*;
	import exception.*;
	
	import runtime.ClassMapFactory;
	
	import expression.primary.*;
}

@members {
	int lineNo=1;
	LinkedList<Method> methodStack = new LinkedList<Method>();
	LinkedList<Class> classStack = new LinkedList<Class>();
	LinkedList<Scope> scopeStack = new LinkedList<Scope>();
}


COMMENT
    :   '/*' ( options {greedy=false;} : . )* '*/' {$channel=HIDDEN;}
    ;

LINE_COMMENT
    : '//' ~('\n'|'\r')* '\r'? '\n' {$channel=HIDDEN;}
    ;

WS  :  (' '|'\r'|'\t'|'\u000C'|'\n') {$channel=HIDDEN;}
    ;	

INTLIT 
	: ('0' | '1'..'9' '0'..'9'*) IntegerTypeSuffix? ;

fragment	
IntegerTypeSuffix : ('l'|'L') ;

DOUBLELIT
    :   ('0'..'9')+ '.' ('0'..'9')* Exponent? DoubleTypeSuffix?
    |   '.' ('0'..'9')+ Exponent? DoubleTypeSuffix?
    |   ('0'..'9')+ Exponent DoubleTypeSuffix?
    |   ('0'..'9')+ DoubleTypeSuffix
    ;
    
fragment
Exponent : ('e'|'E') ('+'|'-')? ('0'..'9')+ ;

fragment
DoubleTypeSuffix : ('f'|'F'|'d'|'D') ;

CHARLIT
	: '\'' . '\''
	;

STRINGLIT
    :  '"' ( ~'"' )* '"'
    ;

CLASS_IDENT
	//options {testLiterals=true;}
	: 'A'..'Z' ('a'..'z'|'A'..'Z'|'0'..'9'|'_')*
	;

IDENT
	//options {testLiterals=true;}
	: ('a'..'z'|'_') ('a'..'z'|'A'..'Z'|'0'..'9'|'_')*
	;

DOT
	: '.'   ;
ASSIGN
	: '='   ;
COLON
	: ':'   ;
SEMI
	: ';'   ;
COMMA
	: ','   ;
EQUALS
	: '=='  ;
LBRACKET
	: '['   ;
RBRACKET
	: ']'   ;
LBRACE
	: '{'   ;
RBRACE
	: '}'   ;
DOTDOT
	: '..'  ;
LPAREN
	: '('   ;
RPAREN
	: ')'   ;
NOT_EQUALS
	: '!='  ;
LTE
	: '<='
	;
LT
	: '<'
	;

GTE
	: '>='
	;

GT
	: '>'
	;

PLUS
	: '+'   ;
MINUS
	: '-'   ;
TIMES
	: '*'   ;
DIV
	: '/'   ;
MOD
	: '%'   ;

//Parser

//class FibsRecognizer extends Parser;

//options {
//  defaultErrorHandler = true;      // Don't generate parser error handlers
//}
main	:
	p=program {
		$p.program.run();
	}
	;

program returns [Program program]
	:
	  cd=classDeclarations 
	  { ClassMapFactory.getInstance().setClassMap($cd.classMap); }
	  sb=scriptBody
	  { $program = new Program($sb.attributes, $sb.statements); }
	;

classDeclarations returns [Map<String, Class> classMap]
	:
	{ $classMap = new HashMap<String, Class>(); }
	  (c=classDeclaration { $classMap.put($c.c.getName(), $c.c); })* 
	;

classDeclaration returns [Class c]
	:
	  'class' id=CLASS_IDENT
	  LBRACE
	  { Map<String, Attribute> attributes = new HashMap<String, Attribute>(); }
	  { Map<String, Method> methods = new HashMap<String, Method>(); }
	  { $c = new Class($id.text, attributes, methods); }
	  { Scope scope = new Scope(null); }
	  	{ classStack.add($c); }
	  (attr=attribute_declaration { attributes.put($attr.attribute.getName(), $attr.attribute); 
	  	scope.addReference(new Reference(attr.attribute)); })*
	  { scopeStack.add(scope); }
	  (m=method_declaration { methods.put($m.method.getMethodName(), $m.method); } )*
	  { scopeStack.getLast(); }
	  	{ classStack.getLast(); }
	  RBRACE
	;
	
method_declaration returns [ Method method ]
	:
		t=type { Type type = $type.type; }IDENT { String name = $IDENT.text; } LPAREN arglist RPAREN
		LBRACE
		{ List<Attribute> args = $arglist.args; }
		{ List<Attribute> declarations = new LinkedList<Attribute>(); }
		{ List<Statement> statements = new LinkedList<Statement>(); }
		{ $method = new Method(classStack.peekLast(), type, name, args, declarations, statements); }
		{ Scope scope = new Scope(scopeStack.peekLast()); }
			{ methodStack.add($method); }
		(dec=attribute_declaration { declarations.add(dec.attribute);
			scope.addReference(new Reference($dec.attribute)); } )*
		{ scopeStack.add(scope); }
		(s=statement { statements.add(s.statement); } )*
		{ scopeStack.getLast(); }
			{ methodStack.getLast(); }
		RBRACE
	;

attribute_declaration returns [Attribute attribute]
	:
		type IDENT {$attribute = new Attribute($IDENT.text, $type.type);} ';'
	;
	
type returns [Type type]
	:
		{int size = -1;}
		typename = ('int' | 'double' | 'char' | 'bool' | 'string' | CLASS_IDENT) ( LBRACKET INTLIT RBRACKET {size = Integer.parseInt($INTLIT.text);})?
		{
			if(size > -1)
				$type = new Type($typename.text, true, size);
			else
				$type = new Type($typename.text, false);
		}
	;
	
arglist returns [List<Attribute> args]
	:
		{$args = new LinkedList<Attribute>(); }
		( t=type id=IDENT { $args.add(new Attribute($id.text, $t.type)); }
		(',' t2=type id2=IDENT { $args.add(new Attribute($id2.text, $t2.type)); } )* )?
	;

scriptBody returns [List<Attribute> attributes, List<Statement> statements]
	:
	{ Scope scope = new Scope(null); }
	{ $attributes = new LinkedList<Attribute>(); }
	{ $statements = new LinkedList<Statement>(); }
	(a=attribute_declaration { $attributes.add($a.attribute); scope.addReference(new Reference($a.attribute));})*
	{ scopeStack.add(scope); }
	(s=statement { $statements.add($s.statement); })*
	{ scopeStack.getLast(); }
	;

statement returns [Statement statement]
	:	a=assignmentStatement { $statement = $a.statement; }
	|	i=ifStatement { $statement = $i.statement; }
	|	w=whileStatement { $statement = $w.statement; }
	|	r=returnStatement { $statement = $r.statement; }
	;

assignmentStatement returns [Statement statement]
	:
	  	v=variable ASSIGN e=expression ';'
		{ $statement = new AssignmentStatement(scopeStack.peekLast(), e.expression, v.ident); }
	;

ifStatement returns [Statement statement]
	:	'if' LPAREN e=expression
		{ List<Statement> trueStatements = new LinkedList<Statement>(); }
		{ List<Statement> falseStatements = new LinkedList<Statement>(); }
		RPAREN LBRACE
			(ts=statement { trueStatements.add($ts.statement); } )* 
			RBRACE
		('else'  LBRACE
			(fs=statement { falseStatements.add($fs.statement); } )*
			RBRACE )?
		{ $statement = new IfStatement(scopeStack.peekLast(), $e.expression, trueStatements, falseStatements); }
	;
	
whileStatement returns [Statement statement]
	:	'while' LPAREN e=expression RPAREN
			LBRACE
			{ List<Statement> statements = new LinkedList<Statement>(); }
			(s=statement { statements.add(s.statement); } )*
			{ $statement = new WhileStatement(scopeStack.peekLast(), $e.expression, statements); }
			RBRACE
	;

returnStatement returns [Statement statement]
	:	'return' e=expression { $statement = new ReturnStatement(scopeStack.peekLast(), methodStack.peekLast(), $e.expression); }
	;
	
	
expression returns [Expression expression]
	:	e=conditionalOrExpression { $expression = e.expression; }
	;
	
conditionalOrExpression returns [Expression expression]
	:	{ List<Expression> expressionList = new LinkedList<Expression>(); }
		e=conditionalAndExpression { expressionList.add(e.expression); }
		( '||' e=conditionalAndExpression { expressionList.add(e.expression); } )*
		{ try {$expression = new ConditionalOrExpression(scopeStack.peekLast(), expressionList);} catch(Exception ex){/* TODO*/} }
	;
	
conditionalAndExpression returns [ConditionalAndExpression expression]
	:	{ List<Expression> expressionList = new LinkedList<Expression>(); }
		e=equalityExpression { expressionList.add(e.expression); }
		( '&&' e=equalityExpression { expressionList.add(e.expression); } )*
		{ try {$expression = new ConditionalAndExpression(scopeStack.peekLast(), expressionList);} catch(Exception ex){/* TODO*/} }
	;
	
equalityExpression returns [EqualityExpression expression]
	:	{ ArrayList<Expression> expressionList = new ArrayList<Expression>(); }
		{ ArrayList<EqualityExpression.Sign> operators = new ArrayList<EqualityExpression.Sign>(); }
		e=relationalExpression { { expressionList.add(e.expression); } }
		( o=( '==' | '!=' ) 
		{ $o.text.intern();
		if($o.text.equals("=="))
			operators.add(EqualityExpression.Sign.EQUAL);
		else
			operators.add(EqualityExpression.Sign.NOT_EQUAL);
		}
		e=relationalExpression { expressionList.add(e.expression); } )*
		{ try{
			$expression = new EqualityExpression(scopeStack.peekLast(), operators, expressionList); 
		  }catch(ExpressionException exc) {}	
		}
	;
	
relationalExpression returns [RelationalExpression expression]
	:	{ ArrayList<Expression> expressionList = new ArrayList<Expression>(); }
		{ ArrayList<RelationalExpression.Sign> operators = new ArrayList<RelationalExpression.Sign>(); }
		e=additiveExpression { { expressionList.add(e.expression); } }
		( o=relationalOp { 
		$o.text.intern();
		if($o.text.equals("<"))
			operators.add(RelationalExpression.Sign.LT);
		else if($o.text.equals(">"))
			operators.add(RelationalExpression.Sign.GT);
		else if($o.text.equals("<="))
			operators.add(RelationalExpression.Sign.LE);
		else
			operators.add(RelationalExpression.Sign.GE);
		 }
		e=additiveExpression { expressionList.add(e.expression); } )*
		
		{ try{
			$expression = new RelationalExpression(scopeStack.peekLast(), expressionList, operators); 
		  }catch(ExpressionException exc) {}
		}
	;
	
relationalOp 
	:	'<' | '>' | '<=' | '>='
	;
	
additiveExpression returns [AdditiveExpression expression]
	:	{ ArrayList<Expression> expressionList = new ArrayList<Expression>(); }
		{ ArrayList<AdditiveExpression.Sign> operators = new ArrayList<AdditiveExpression.Sign>(); }
		e=multiplicativeExpression { { expressionList.add(e.expression); } }
		( o=( '+' | '-' )
		{
		if($o.text.equals("+"))
			operators.add(AdditiveExpression.Sign.PLUS);
		else
			operators.add(AdditiveExpression.Sign.MINUS);
		}
		e=multiplicativeExpression { { expressionList.add(e.expression); } })*
		{ try{	
			$expression = new AdditiveExpression(scopeStack.peekLast(), expressionList, operators); 
		  }catch(ExpressionException exc) {}
		} 
		
	;
	
multiplicativeExpression returns [MultiplicativeExpression expression]
	:	{ ArrayList<Expression> expressionList = new ArrayList<Expression>(); }
		{ ArrayList<MultiplicativeExpression.Sign> operators = new ArrayList<MultiplicativeExpression.Sign>(); }
		e=unaryExpression { { expressionList.add(e.expression); } }
		( o=( '*' | '/' )
		{
		if($o.text.equals("*"))
			operators.add(MultiplicativeExpression.Sign.MULTIPLICATION);
		else
			operators.add(MultiplicativeExpression.Sign.DIVISION);
		}
		e=unaryExpression { { expressionList.add(e.expression); } } )*
		{  try{	
			$expression = new MultiplicativeExpression(scopeStack.peekLast(), expressionList, operators); 
		  }catch(ExpressionException exc) {  } }
	;
	

unaryExpression returns [UnaryExpression expression]
	:	{ UnaryExpression.Sign oper = null; Expression expr = null;} 
	(
		'+' e=unaryExpression
			{ oper = UnaryExpression.Sign.PLUS; expr = e.expression;}
	|	'-' e=unaryExpression
			{ oper = UnaryExpression.Sign.MINUS; expr = e.expression;}
	|	ue=negationExpression
			{ oper = UnaryExpression.Sign.NONE; expr = ue.expression;}
	)
		{
		try{
			$expression = new UnaryExpression(scopeStack.peekLast(), expr, oper);
		}catch(ExpressionException exc) {}
		}
	;

negationExpression returns [NegationExpression expression]
	:	{ NegationExpression.Sign sign = null; Expression expr = null; }
	(
		'!' e=negationExpression { sign = NegationExpression.Sign.NOT; expr = e.expression; }
	|	p=primary { sign = NegationExpression.Sign.NONE; expr = p.expression; }
	)
		{ 
		try{
			$expression = new NegationExpression(scopeStack.peekLast(), expr, sign);
		}catch(ExpressionException exc) { }
		}
	;

	
	
primary returns [PrimaryExpression expression]
	:	{ Expression expr = null; PrimaryExpression.PrimaryType type = null;}
	(	LPAREN e=expression RPAREN { expr = $e.expression; type = PrimaryExpression.PrimaryType.expression; }
	|	lit=literal { expr = $lit.literal; type = PrimaryExpression.PrimaryType.literal; }
	|	s=symbol { expr = $s.ident; type = PrimaryExpression.PrimaryType.symbol; }
	|	'new' c=creator { expr = $c.creator; type = PrimaryExpression.PrimaryType.creator; } )
		{ try{
			$expression = new PrimaryExpression(scopeStack.peekLast(), expr, type); 
		  }catch(Exception exc) {}
		}
	;
	
creator	returns [Creator creator]
	:	{ List<Expression> args = new LinkedList<Expression>(); }
		id=CLASS_IDENT LPAREN RPAREN
		{$creator = new Creator(scopeStack.peekLast(), $id.text); }
	;
	
literal returns [Literal literal]
    :   { Object val = null; String type = null;}
    	(INTLIT { val = new Integer($INTLIT.text); type = "int";}
    |   DOUBLELIT { val = new Double($DOUBLELIT.text); type = "double";}
    |   CHARLIT { val = new Character($CHARLIT.text.charAt(1)); type = "char";}
    |   STRINGLIT { val = $STRINGLIT.text.substring(1,$STRINGLIT.text.length()-1); type = "string";}
    |   boollit { val = new Boolean($boollit.text); type = "bool";}
    )
    { $literal = new Literal(scopeStack.peekLast(), type, val); }
    ;

boollit	:	'true' | 'false' ;

symbol returns [Ident ident]
	:	variable { $ident = $variable.ident; }
	|	methodCall { $ident = $methodCall.ident; }
	;

methodCall returns [Ident ident]
	:	{ List<Selector> selectors = new LinkedList<Selector>(); }
		IDENT m=callArgs { selectors.add(new Selector("call", new MethodCall($m.expressions, $IDENT.text))); } 
		(selector { selectors.add($selector.selector); } )*
		{ $ident = new Ident(scopeStack.peekLast(), $IDENT.text, selectors); }
	;

variable returns [Ident ident]
	:	{ List<Selector> selectors = new LinkedList<Selector>(); }
		IDENT (selector { selectors.add($selector.selector); } )*
		{ $ident = new Ident(scopeStack.peekLast(), $IDENT.text, selectors); }
	;

selector returns [Selector selector]
	:	LBRACKET INTLIT RBRACKET { $selector = new Selector("index", Integer.parseInt($INTLIT.text) ); }
	|	'.' IDENT { String type = "attribute"; Object value = $IDENT.text; }
		(callArgs { type = "call"; value = new MethodCall($callArgs.expressions, $IDENT.text); } )?
		{ $selector = new Selector(type, value); }
	;
	
callArgs returns [List<Expression> expressions]
	:	{ $expressions = new LinkedList<Expression>(); }
		LPAREN (expressionList { $expressions = $expressionList.expressions; } )? RPAREN
		
	;
	
expressionList returns [List<Expression> expressions]
	:	{ $expressions = new LinkedList<Expression>(); }
		e1=expression { $expressions.add(e1.expression); }
		( ',' e2=expression { $expressions.add(e2.expression); } )*
	;
